﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Windows;
using Plugins.Core;
using Plugins.Historical;
using StockModel;
using Utils;

namespace Plugins.Base.Historical
{
	public abstract class HistoricalEngineBase : PropertyChangedNotifier
	{
		#region Attributes

		private int _totalDays;
		private int _executedDays;

		#endregion

		#region Properties

		protected QuotationDataSource QuotationDataSource { get; private set; }
		protected RawStockDataSource RawStockDataSource { get; private set; }

		protected List<Operation> Operations { get; private set; }
		protected List<FrameworkElement> CustomData { get; private set; }
		protected List<Stock> StocksToProcess { get; private set; }

		public HistoricalSettingsBase Settings { get; protected set; }

		public int TotalDaysToExecute
		{
			get
			{
				return _totalDays;
			}
			protected set
			{
				if (_totalDays != value)
				{
					_totalDays = value;
					base.OnPropertyChanged("TotalDaysToExecute");
				}
			}
		}

		public int ExecutedDays
		{
			get
			{
				return _executedDays;
			}
			protected set
			{
				if (_executedDays != value)
				{
					_executedDays = value;
					base.OnPropertyChanged("ExecutedDays");
				}
			}
		}

		#endregion

		#region Events

		public event EventHandler<HistoricalResultsAvailableEventArgs> ResultsAvailable;

		#endregion

		#region Public Methods

		protected HistoricalEngineBase(QuotationDataSource quotationDataSource, RawStockDataSource rawStockDataSource)
		{
			this.QuotationDataSource = quotationDataSource;
			this.RawStockDataSource = rawStockDataSource;

			this.Operations = new List<Operation>();
			this.CustomData = new List<FrameworkElement>();
		}

		public virtual void Execute()
		{
			try
			{
				this.InitializeEngine();

				List<HistoricalItemToProcess> itemsToProcess = this.FindItemsToProcess();
				this.TotalDaysToExecute = itemsToProcess.Count;

				using (HistoricalDayProcessorBase processor = this.CreateDayProcessor())
				{
					foreach (HistoricalItemToProcess historicalItemToProcess in itemsToProcess)
					{
						processor.ProcessHistoricalItem(historicalItemToProcess);

						this.ExecutedDays++;

						this.Operations.AddRange(processor.Operations);
						this.CustomData.AddRange(processor.CustomData);

						processor.Operations.Clear();
						processor.CustomData.Clear();
					}
				}

				this.OnResultsAvailable(this.Operations, this.CustomData);
			}
			finally
			{
				this.ClearEngine();
			}
		}

		protected List<HistoricalItemToProcess> FindItemsToProcess()
		{
			this.StocksToProcess = this.RawStockDataSource.GetStocks(this.Settings.StockSelectionOptions.SelectedStockName).ToList();
			return this.FindItemsToProcess(this.StocksToProcess);
		}

		protected List<HistoricalItemToProcess> FindItemsToProcess(IEnumerable<Stock> stocks)
		{
			List<HistoricalItemToProcess> result = new List<HistoricalItemToProcess>();

			foreach (Stock stock in stocks)
			{
				IEnumerable<DateTime> days = this.QuotationDataSource.GetAvailableDays(stock);
				foreach (DateTime day in days)
				{
					// Set the max and min days to know the number of days processed. These values will be used when publishing results
					if (this.Settings.DateSettings.UseAllDays && (this.Settings.DateSettings.StartDate == null || this.Settings.DateSettings.StartDate > day))
					{
						this.Settings.DateSettings.StartDate = day;
					}

					if (this.Settings.DateSettings.UseAllDays && (this.Settings.DateSettings.EndDate == null || this.Settings.DateSettings.EndDate < day))
					{
						this.Settings.DateSettings.EndDate = day;
					}

					if (this.Settings.DateSettings.UseAllDays || (day >= this.Settings.DateSettings.StartDate && day <= this.Settings.DateSettings.EndDate))
					{
						result.Add(new HistoricalItemToProcess(day, stock));
					}
				}
			}

			return result.OrderBy(r => r.Date).ToList();
		}

		protected virtual void OnResultsAvailable(IEnumerable<Operation> operations, IEnumerable<FrameworkElement> customData)
		{
			if (this.Settings.DateSettings.StartDate == null || this.Settings.DateSettings.EndDate == null)
			{
				throw new InvalidOperationException("The Start or EndDate have not been set so cannot calculate the number of days processed");
			}

			if (this.ResultsAvailable != null)
			{
				HistoricalResultsAvailableEventArgs e = new HistoricalResultsAvailableEventArgs(this.Settings.DateSettings.StartDate.Value, this.Settings.DateSettings.EndDate.Value);

				e.Result.Operations.AddRange(operations);
				e.CustomData.AddRange(customData);

				this.ResultsAvailable(this, e);
			}
		}

		protected virtual void InitializeEngine()
		{
			this.ExecutedDays = 0;
		}

		protected virtual void ClearEngine()
		{
			this.Operations.Clear();
			this.CustomData.Clear();

			if (this.StocksToProcess != null)
			{
				this.StocksToProcess.ForEach(s => s.Dispose());
				this.StocksToProcess.Clear();
			}
		}

		protected abstract HistoricalDayProcessorBase CreateDayProcessor();

		#endregion
	}
}
